package org.server.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.server.action.ActionCmd;
import org.server.common.BroadcastUtils;
import org.server.common.exception.CustomException;
import org.server.common.RedisUtil;
import org.server.domain.Result.Msg;
import org.server.domain.Room.Poker;
import org.server.task.RoomDealTask;
import org.server.task.RoomPlayingTask;
import org.server.task.TimerTaskEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 房间类
 *
 * @author tang
 **/
@Slf4j
@Component
public class RoomService {
    /**
     * 玩家人数常量
     *
     * @author Tang
     * @date 2023/3/2 12:58
     **/
    public final static Integer MAX_PLAYER = 4;
    public final static Integer MIN_PLAYER = 0;
    /**
     * 房间状态常量
     * IDLE房间空闲状态
     * READY房间准备状态（玩家列表不为空）
     * PLAYING房间正在游戏（所有玩家准备后，进入此状态）
     *
     * @author Tang
     * @date 2023/3/2 12:58
     **/
    public final static Integer ROOM_IDLE = 0;
    public final static Integer ROOM_READY = 1;
    public final static Integer ROOM_PLAYING = 2;
    /**
     * 房间表名
     *
     * @author Tang
     * @date 2023/4/18 23:25
     **/
    public final static String ROOM_RECORD = "ROOM_RECORD";
    public final static String PLAY_ACTIVITY = "PLAY_ACTIVITY";
    public final static String CURRENT = "current";
    public final static String DECK = "deck";
    public final static String PLAY_LIST = "player_list";
    public final static String STATUS = "status";
    public final static String PASS = "pass";
    public final static String TRUE_OR_FALSE = "true_or_false";
    public final static String READY = "ready";
    public final static String LAST_PLAY_PLAYER = "last_play_player";
    public final static String CURRENT_FAKER_CARD = "current_faker_card";
    public final static Integer ALIVE_TIME = 300;
    public final static Integer DELAY_TIME = 5000;
    public final static Integer MIN_PASS = 0;
    public final static Integer MAX_PASS = 3;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PokerService pokerService;
    @Autowired
    private LoginService loginService;

    /**
     * 创建游戏房间
     *
     * @param roomName
     * @return void
     * @author Tang
     * @date 2023/3/28 23:20
     **/
    public void createRoom(String roomName, Long userId) {
        String existName = (String) redisUtil.getValueInHash(ROOM_RECORD, userId.toString());
        if (existName != null && !existName.isEmpty()) {
            if (redisUtil.isExistHash(existName)) {
                throw new CustomException(existName);
            }
        }
        //设置房间的初始状态
        redisUtil.setHash(roomName, STATUS, ROOM_IDLE);
        //设置玩家列表
        redisUtil.setHash(roomName, PLAY_LIST, JSON.toJSONString(new ArrayList<Long>()));
        //设置房间的准备人数
        redisUtil.setHash(roomName, READY, MIN_PLAYER);
        //设置房间存活时间为5min
        redisUtil.expire(roomName, ALIVE_TIME);
        //初始化过牌人数
        redisUtil.setHash(roomName, PASS, MIN_PASS);
        //初始化牌堆
        redisUtil.setHash(roomName, DECK, JSON.toJSONString(new Stack<>()));
        //初始化真假堆
        redisUtil.setHash(roomName, TRUE_OR_FALSE, JSON.toJSONString(new ArrayList<>()));
        //记录该玩家
        redisUtil.setHash(ROOM_RECORD, userId.toString(), roomName);
        log.info("创建房间成功");
    }

    /**
     * 加入房间
     *
     * @param roomName
     * @param userId
     * @return void
     * @throws CustomException
     * @author Tang
     * @date 2023/3/28 23:20
     **/
    public List<Long> joinRoom(String roomName, Long userId) throws CustomException {
        //从redis中找房间列表
        List<Long> playerList = JSONArray.parseArray((String) redisUtil.getValueInHash(roomName, PLAY_LIST), Long.class);
        judgeJoinRoom(playerList, userId);
        //读取房间状态
        Integer status = (Integer) redisUtil.getValueInHash(roomName, STATUS);
        if (status.equals(ROOM_PLAYING)) {
            throw new CustomException("别来碰");
        }

        playerList.add(userId);
        //更新redis中的数据
        redisUtil.setHash(roomName, PLAY_LIST, JSON.toJSONString(playerList));
        if (status.equals(ROOM_IDLE)) {
            //如果房间状态为初始状态，在加入玩家后，将房间设置为永不过期并将房间状态设置为房间准备状态
            redisUtil.setHash(roomName, STATUS, ROOM_READY);
            redisUtil.persist(roomName);
        }
        //记录该玩家进入的房间
        redisUtil.setHash(PLAY_ACTIVITY, userId.toString(), roomName);
        return playerList;
    }

    /**
     * 退出房间
     *
     * @param roomName
     * @param userId
     * @return void
     * @author Tang
     * @date 2023/3/28 23:46
     **/
    public List<Long> leftRoom(String roomName, Long userId) throws CustomException {
        //从redis中找房间列表
        List<Long> playerList = JSONArray.parseArray((String) redisUtil.getValueInHash(roomName, PLAY_LIST), Long.class);
        //判断房间属性
        judgeLeftRoom(playerList);
        //判断玩家是否存在
        if (!playerList.contains(userId)) {
            throw new CustomException("该玩家不存在");
        }
        //删除玩家加入房间的记录
        redisUtil.removeValueInHash(PLAY_ACTIVITY, userId.toString());
        //没有玩家，则给删除房间
        playerList.remove(userId);
        if (playerList.size() == MIN_PLAYER) {
            redisUtil.removeHash(roomName);
            TimerTaskEnum.ROOM_PLAYING.removeTimerTask(roomName);
            return null;
        }
        //更新redis中的数据
        redisUtil.setHash(roomName, PLAY_LIST, JSON.toJSONString(playerList));
        return playerList;
    }

    /**
     * 玩家准备
     *
     * @param roomName
     * @param userId
     * @return void
     * @author Tang
     * @date 2023/3/29 0:18
     **/
    public List<Long> ready(String roomName, Long userId) throws CustomException {
        //获取玩家列表
        List<Long> playerList = JSONArray.parseArray((String) redisUtil.getValueInHash(roomName, PLAY_LIST), Long.class);
        //判断房间是否存在
        isNullRoom(playerList);
        if (redisUtil.getValueInHash(roomName, userId.toString()) != null) {
            throw new CustomException("玩家已准备");
        }
        //增加准备人数
        Integer ready = (Integer) redisUtil.getValueInHash(roomName, READY) + 1;
        if (ready.equals(MAX_PLAYER)) {
            redisUtil.setHash(roomName, STATUS, ROOM_PLAYING);
            redisUtil.setHash(roomName, CURRENT, playerList.get(0));
            new RoomDealTask()
                    .setPokerService(pokerService)
                    .setPlayerList(playerList)
                    .setRoomName(roomName)
                    .setRedisUtil(redisUtil)
                    .setLoginService(loginService)
                    .setDelayExecutionTime(DELAY_TIME)
                    .task();
        }
        redisUtil.setHash(roomName, READY, ready);
        redisUtil.setHash(roomName, userId.toString(), "");
        return playerList;
    }

    /**
     * 取消准备
     *
     * @param roomName
     * @param userId
     * @return java.util.List<java.lang.Long>
     * @author Tang
     * @date 2023/4/19 23:29
     **/
    public List<Long> cancelReady(String roomName, Long userId) throws CustomException {
        //获取玩家列表
        List<Long> playerList = JSONArray.parseArray((String) redisUtil.getValueInHash(roomName, PLAY_LIST), Long.class);
        //判断房间是否存在
        isNullRoom(playerList);
        //减少准备人数
        Integer ready = (Integer) redisUtil.getValueInHash(roomName, READY) - 1;
        if (ready < MAX_PLAYER) {
            redisUtil.setHash(roomName, STATUS, ROOM_READY);
            TimerTaskEnum.ROOM_READY.removeTimerTask(roomName);
        }
        redisUtil.setHash(roomName, READY, ready);
        return playerList;
    }

    /**
     * 玩家出牌
     *
     * @param poker
     * @param userId
     * @return java.util.List<java.lang.Long>
     * @author Tang
     * @date 2023/4/24 10:44
     **/
    public List<Long> play(Poker poker, Long userId) {
        //获取基本信息
        String roomName = poker.roomName;
        Integer isTrue = poker.getIsTrue();
        List<Integer> realPokers = JSON.parseArray(poker.realPokers, Integer.class);
        List<Integer> fakerPokers = JSON.parseArray(poker.fakePoker, Integer.class);

        Object object = redisUtil.getValueInHash(roomName, CURRENT_FAKER_CARD);
        String fakerCard = object == null ? null : object.toString();
        if (fakerCard == null || fakerCard.isEmpty()) {
            //本轮回合由当前玩家开启
            redisUtil.setHash(roomName, CURRENT_FAKER_CARD, fakerPokers.get(0));
        } else {
            //跟牌判断
            String faker = fakerPokers.get(0).toString();
            if (!faker.equals(fakerCard)) {
                throw new CustomException("未跟牌上一位玩家");
            }
        }

        //获取当前出牌的玩家id
        long currentId = Long.parseLong(redisUtil.getValueInHash(roomName, CURRENT).toString());
        if (!userId.equals(currentId)) {
            throw new CustomException("未在你的回合");
        }

        //减少手牌
        List<Integer> pokers = JSON.parseArray(redisUtil.getValueInHash(roomName, Long.toString(currentId)).toString(), Integer.class);
        if (!new HashSet<>(pokers).containsAll(realPokers) || realPokers.size() == 0) {
            log.error("{}", realPokers);
            throw new CustomException("数据有误");
        }
        pokers.removeAll(realPokers);
        //更新该玩家的手牌
        redisUtil.setHash(roomName, Long.toString(currentId), JSON.toJSONString(pokers));

        //更新牌堆
        Stack<List<Integer>> deck = new Stack<>();
        String deckInRedis = redisUtil.getValueInHash(roomName, DECK).toString();
        if (deckInRedis != null) {
            for (Integer[] ints : JSON.parseArray(deckInRedis, Integer[].class)) {
                deck.push(Arrays.asList(ints));
            }
        }
        deck.push(realPokers);
        redisUtil.setHash(roomName, DECK, JSON.toJSONString(deck));

        //维护真假堆
        if (isTrue != null) {
            List<Boolean> trueOrFalse = JSON.parseArray(redisUtil.getValueInHash(roomName, TRUE_OR_FALSE).toString(), Boolean.class);
            trueOrFalse.add(isTrue == 1);
            redisUtil.setHash(roomName, TRUE_OR_FALSE, JSON.toJSONString(trueOrFalse));
        }

        //记录当前玩家为最后一个出牌者
        redisUtil.setHash(roomName, LAST_PLAY_PLAYER, Long.toString(currentId));

        //更新过牌人数-将其置为0
        redisUtil.setHash(roomName, PASS, MIN_PASS);

        //获取其它玩家的id
        List<Long> userIds = JSON.parseArray(redisUtil.getValueInHash(roomName, PLAY_LIST).toString(), Long.class);

        //开启下一个回合
        new RoomPlayingTask()
                .setPlayerList(userIds)
                .setRoomName(roomName)
                .setRedisUtil(redisUtil)
                .setLoginService(loginService)
                .setCacheKey(roomName)
                .setDelayExecutionTime(RoomPlayingTask.FINISH_TIME)
                .task();
        return userIds;
    }

    /**
     * 玩家过牌
     *
     * @param poker
     * @param userId
     * @return java.util.List<java.lang.Long>
     * @author Tang
     * @date 2023/4/24 22:18
     **/
    public List<Long> pass(Poker poker, Long userId) {
        //获取基本信息
        String roomName = poker.roomName;
        //获取当前出牌的玩家id
        long currentId = Long.parseLong(redisUtil.getValueInHash(roomName, CURRENT).toString());
        if (!userId.equals(currentId)) {
            throw new CustomException("未在你的回合");
        }
        //获取其它玩家的id
        List<Long> userIds = JSON.parseArray(redisUtil.getValueInHash(roomName, PLAY_LIST).toString(), Long.class);
        new RoomPlayingTask()
                .setPlayerList(userIds)
                .setRoomName(roomName)
                .setRedisUtil(redisUtil)
                .setLoginService(loginService)
                .setCacheKey(roomName)
                .setDelayExecutionTime(RoomPlayingTask.FINISH_TIME)
                .task();
        return userIds;
    }

    /**
     * 玩家质疑
     *
     * @param poker
     * @param userId
     * @return java.util.List<java.lang.Long>
     * @author Tang
     * @date 2023/4/24 22:18
     **/
    public List<Long> question(Poker poker, Long userId) {
        String roomName = poker.getRoomName();
        List<Long> userList = JSON.parseArray(redisUtil.getValueInHash(roomName, PLAY_LIST).toString(), Long.class);
        long currentId = Long.parseLong(redisUtil.getValueInHash(roomName, CURRENT).toString());
        if (!userId.equals(currentId)) {
            throw new CustomException("未在你的回合");
        }
        //玩家质疑逻辑
        List<Boolean> trueOrFalse = JSON.parseArray(redisUtil.getValueInHash(roomName, TRUE_OR_FALSE).toString(), Boolean.class);
        if (trueOrFalse.size() > 0) {
            //读取牌堆和真假堆的数据
            boolean isTrue = trueOrFalse.get(trueOrFalse.size() - 1);
            String deckInRedis = redisUtil.getValueInHash(roomName, DECK).toString();
            List<Integer> deck = new ArrayList<>();
            JSON.parseArray(deckInRedis, Integer[].class).forEach(i -> {
                deck.addAll(List.of(i));
            });
            deckInRedis = JSON.toJSONString(deck);
            redisUtil.setHash(roomName, DECK, JSON.toJSONString(new Stack<>()));
            redisUtil.setHash(roomName, TRUE_OR_FALSE, JSON.toJSONString(new ArrayList<>()));
            log.error("{}", trueOrFalse);
            log.error("isTrue:{},{}", isTrue, userId);
            if (isTrue) {
                //质疑失败把牌给被质疑者
                //发送给客户端
                BroadcastUtils.broadcast(ActionCmd.POKER, ActionCmd.POKER_DEAL, new Msg(deckInRedis), userId);
                new RoomPlayingTask()
                        .setPlayerList(userList)
                        .setRoomName(roomName)
                        .setLoginService(loginService)
                        .setRedisUtil(redisUtil)
                        .setCacheKey(roomName)
                        .setDelayExecutionTime(RoomPlayingTask.FINISH_TIME)
                        .task();
            } else {
                //质疑成功则把牌给质疑者
//                int indexOf = userList.indexOf(userId);
//                indexOf = indexOf - 1 < 0 ? indexOf - 1 + userList.size() : indexOf - 1;
//                userId = userList.get(indexOf);
                userId = Long.valueOf(redisUtil.getValueInHash(roomName, RoomService.LAST_PLAY_PLAYER).toString());
                //发送给客户端
                BroadcastUtils.broadcast(ActionCmd.POKER, ActionCmd.POKER_DEAL, new Msg(deckInRedis), userId);
                new RoomPlayingTask()
                        .setPlayerList(userList)
                        .setRoomName(roomName)
                        .setRedisUtil(redisUtil)
                        .setLoginService(loginService)
                        .setCacheKey(roomName)
                        .setDelayExecutionTime(RoomPlayingTask.FINISH_TIME)
                        .task();
            }
            log.error("{}", userId);
            //将牌发给 被质疑者或者质疑者
            //更新服务端
            List<Integer> userPoker = JSON.parseArray(redisUtil.getValueInHash(roomName, Long.toString(userId)).toString(), Integer.class);
            userPoker.addAll(deck);
            redisUtil.setHash(roomName, Long.toString(userId), JSON.toJSONString(userPoker));
            //初始化牌堆
            redisUtil.setHash(roomName, DECK, JSON.toJSONString(new Stack<>()));
            //初始化真假堆
            redisUtil.setHash(roomName, TRUE_OR_FALSE, JSON.toJSONString(new ArrayList<>()));
            //更新过牌人数-将其置为0
            redisUtil.setHash(roomName, PASS, MIN_PASS);
            //清空牌的种类
            redisUtil.setHash(roomName,RoomService.CURRENT_FAKER_CARD,"");
        } else {
            throw new CustomException("牌堆暂时无牌，请不要进行质疑");
        }
        return userList;
    }

    private void isNullRoom(List<Long> playerList) throws CustomException {
        //未找到该房间
        if (playerList == null) {
            throw new CustomException("房间不存在");
        }
    }

    private void judgeJoinRoom(List<Long> playerList, Long userId) throws CustomException {
        isNullRoom(playerList);
        //如果玩家在房间内，则加入房间失败
        if (playerList.contains(userId)) {
            throw new CustomException("已在房间内");
        }
        //房间已满的情况
        if (playerList.size() == MAX_PLAYER) {
            throw new CustomException("房间人数已满");
        }
    }

    private void judgeLeftRoom(List<Long> playerList) throws CustomException {
        isNullRoom(playerList);
    }

}
